---
title: Indexes
description: Composing entity indexes
keywords:
  - electrodb
  - docs
  - concepts
  - dynamodb
  - query
  - entity
  - attribute
  - schema
  - index
layout: ../../../layouts/MainLayout.astro
---

When using ElectroDB, indexes are referenced by their `AccessPatternName`. This allows you to maintain generic index names on your DynamoDB table, but reference domain specific names while using your ElectroDB Entity. These will be referenced as _"Access Patterns"_.

All DynamoDB tables start with at least a PartitionKey with an optional SortKey, this can be referred to as the _"Table Index"_. The `indexes` object requires at least the definition of this _Table Index_ **Partition Key** and (if applicable) **Sort Key**.

In your model, the _Table Index_ this is expressed as an _Access Pattern_ _without_ an `index` property. For Secondary Indexes (both GSIs and LSIs), use the `index` property to define the name of the index as defined on your DynamoDB table.

> The 'index' property is simply a mapping of your AccessPatternName to your DynamoDB index name. They allow you to easily create composite keys, and dictate the order of in which your composite attributes are applied. ElectroDB does not create or alter DynamoDB tables, so your indexes will need to be created prior to use.

Within these _AccessPatterns_, you define the PartitionKey and (optionally) SortKeys that are present on your DynamoDB table and map the key's name on the table with the `field` property.

## Index Definition

```typescript
indexes: {
	[AccessPatternName]: {
		index?: string;
		collection?: string | string[];
		type?: 'isolated' | 'clustered';
		pk: {
			field: string;
			composite: AttributeName[];
			template?: string;
			cast?: 'string' | 'number';
			casing?: 'upper' | 'lower' | 'none';
		},
		sk?: {
			field: string;
			composite: AttributesName[];
			template?: string;
			cast?: 'string' | 'number';
			casing?: 'upper' | 'lower' | 'none';
		},
	}
}
```

### Index Options

| Property       |                 Type                 | Required | Description                                                                                                                                                                                                                                                                                                                                 |
| -------------- | :----------------------------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index`        |               `string`               |    no    | Required when the `Index` defined is a _Global/Local Secondary Index_; but is omitted for the table's primary index.                                                                                                                                                                                                                        |
| `collection`   |         `string`, `string[]`         |    no    | Used when models are joined to a `Service`. When two entities share a `collection` on the same `index`, they can be queried with one request to DynamoDB. The name of the collection should represent what the query would return as a pseudo `Entity`; See the page on [Collections](/en/modeling/collections) for more information on this functionality. |
| `type`         |       `isolated`, `clustered`        |    no    | Allows you to optimize your index for either [entity isolation](/en/modeling/indexes#isolated-indexes) (high volume of records per partition) or [entity relationships](/en/modeling/indexes#isolated-indexes) (high relationship density per partition). When omitted, ElectroDB defaults to `isolation`.                                                     |
| `condition`    |        `(attr: T) => boolean`        |    no    | A function that accepts all attributes provided every mutation method and returns a boolean value. When provided, ElectroDB will use this function to determine if an index should be be written given a provided set of attributes. This is useful for implementing "sparse" indexes, See the second on [Sparse Indexes](/en/modeling/indexes#sparse-indexes) below for more information on this functionality |
| `pk`           |               `object`               |   yes    | Configuration for the pk of that index or table                                                                                                                                                                                                                                                                                             |
| `pk.composite` |              `string[]`              |   yes    | An array that represents the order in which attributes are concatenated to composite attributes the key (see [Composite Attributes](#composing-attributes) below for more on this functionality).                                                                                                                                           |
| `pk.template`  |               `string`               |    no    | A string that represents the template in which attributes composed to form a key (see [Composite Attribute Templates](#composite-attribute-templates) below for more on this functionality).                                                                                                                                                |
| `pk.field`     |               `string`               |   yes    | The name of the index Partition Key field as it exists in DynamoDB, if named differently in the schema attributes.                                                                                                                                                                                                                          |
| `pk.casing`    | `default`, `upper`, `lower`, `none`  |    no    | Choose a case for ElectroDB to convert your keys to, to avoid casing pitfalls when querying data. Default: `lower`.                                                                                                                                                                                                                         |
| `pk.cast`      |          `string`, `number`          |    no    | The `cast` option allows you to define a different primitive type for your key than your attribute. When `number` is used, you are only allowed to have one composite attribute, string values are validated at runtime with `parseInt`, and booleans are cast to `1` or `0`. Default: `string`.                                            |
| `sk`           |               `object`               |    no    | Configuration for the sk of that index or table                                                                                                                                                                                                                                                                                             |
| `sk.composite` |              `string[]`              |    no    | Either an Array that represents the order in which attributes are concatenated to composite attributes the key, or a String for a composite attribute template. (see [Composite Attributes](#composite-attributes) below for more on this functionality).                                                                                   |
| `sk.template`  |               `string`               |    no    | A string that represents the template in which attributes composed to form a key (see [Composite Attribute Templates](#composite-attribute-templates) below for more on this functionality).                                                                                                                                                |
| `sk.field`     |               `string`               |   yes    | The name of the index Sort Key field as it exists in DynamoDB, if named differently in the schema attributes.                                                                                                                                                                                                                               |
| `sk.casing`    | `default`, `upper`, `lower`, `none`, |    no    | Choose a case for ElectroDB to convert your keys to, to avoid casing pitfalls when querying data. Default: `lower`.                                                                                                                                                                                                                         |
| `sk.cast`      |          `string`, `number`          |    no    | The `cast` option allows you to define a different primitive type for your key than your attribute. When `number` is used, you are only allowed to have one composite attribute, string values are validated at runtime with `parseInt`, and booleans are cast to `1` or `0`. Default: `string`.                                            |

## Composing Indexes

A **Composite Attribute** is a segment of a key based on one of the attributes. **Composite Attributes** are concatenated together from either a **Partition Key**, or a **Sort Key**, which define an `index`.

> Only attributes with a type of `"string"`, `"number"`, `"boolean"`, or `string[]` (enum) can be used as composite attributes.

There are two ways to provide composite:

1. As a [Composite Attribute Array](#composite-attribute-arrays)
2. As a [Composite Attribute Template](#composite-attribute-templates)

For example, in the following **Access Pattern**, "`locations`" is made up of the composite attributes `storeId`, `mallId`, `buildingId` and `unitId` which map to defined attributes in the [schema](/en/modeling/schema):

#### Input

```json
{
    "storeId": "STOREVALUE",
    "mallId": "MALLVALUE",
    "buildingId": "BUILDINGVALUE",
    "unitId": "UNITVALUE"
};
```

#### Output

```json
{
  "pk": "$mallstoredirectory_1#storeId_storevalue",
  "sk": "$mallstores#mallid_mallvalue#buildingid_buildingvalue#unitid_unitvalue"
}
```

For `PK` values, the `service` and `version` values from the model are prefixed onto the key.

For `SK` values, the `entity` value from the model is prefixed onto the key.

### Composite Attribute Arrays

Within a Composite Attribute Array, each element is the name of the corresponding Attribute defined in the Model. The attributes chosen, and the order in which they are specified, will translate to how your composite keys will be built by ElectroDB.

> If the Attribute has a `label` property, that will be used to prefix the composite attributes, otherwise the full Attribute name will be used.

#### Example

```typescript
attributes: {
	storeId: {
		type: "string",
		label: "sid",
	},
	mallId: {
		type: "string",
		label: "mid",
	},
	buildingId: {
		type: "string",
		label: "bid",
	},
	unitId: {
		type: "string",
		label: "uid",
	}
},
indexes: {
	locations: {
		pk: {
			field: "pk",
			composite: ["storeId"]
		},
		sk: {
			field: "sk",
			composite: ["mallId", "buildingId", "unitId"]
		}
	}
}
```

#### Input

```json
{
    "storeId": "STOREVALUE",
    "mallId": "MALLVALUE",
    "buildingId": "BUILDINGVALUE",
    "unitId": "UNITVALUE"
};
```

#### Output

```json
{
  "pk": "$mallstoredirectory_1#sid_storevalue",
  "sk": "$mallstores#mid_mallvalue#bid_buildingvalue#uid_unitvalue"
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gDoFPnAC8cVBQDuKdJiwAKBBzhwQERtQBciVWrgV52XWSFQRZADT61fClABuwWhVMgAhlSqNgF+tBY1rZwjg58wLymAIxk+rg2ah4wDMDMAK4wIroqBnDmFACSjDkhathgbuQCUMCoAObBeWpUHsw61cCMTQYJIZ7exaXNcBVVZqkNPXmt7VTuXdN4iQYZwD519UN6I2OmNZtLLW0dZMyLK2p9eemomNu5zXvVk42XBrOn6RchuPErdS0AA9sjsPhBaMlIvxhs0wABrWEjAjAaglcgIo4GHjgCARLK6ADaE2ExTIAF0yssqXxEWDkaifPt4Vi1DjIPiqsSBlQyVZyGsNg0+eRbvdupSRn88tK8BwEohRm0aKYsBB0lAAPowZUUTWoDwgChkPAASk4HAKfH0ADowJllCECtsyABlAAqAHkAErIABqAEEADIAVWQSx5LoAssGg4HQ+H3oLfML0WQAEIhwpBgAihQAcgBxeNhpZimAukP5wrukvh+Km231CCKc1AA)

### Composite Attribute Templates

You may have found examples online that demonstrate how to make keys for Single Table Design. These patterns often look like `user#${id}` or `org#${id}`. ElectroDB creates keys similar to these patterns out of the box without the need for using "template". It is _highly_ recommended to only use "template" when you are attempting to use ElectroDB on an existing table/dataset. If you are starting a new project, you should not need to use "template", and using it will limit some protections and features granted by ElectroDB.

With a Composite Template, you provide a formatted template for ElectroDB to use when making keys. Composite Attribute Templates allow for potential ElectroDB adoption on already established tables and records.

Attributes are identified by surrounding the attribute with `${...}` braces. For example, the syntax `${storeId}` will match `storeId` attribute in the model.

Convention for a composing a key use the `#` symbol to separate attributes, and for labels to attach with underscore. For example, when composing both the `mallId` and `buildingId` would be expressed as `mid_${mallId}#bid_${buildingId}`.

> **ElectroDB** will not prefix templated keys with the Entity, Project, Version, or Collection. This will give you greater control of your keys but will limit **_ElectroDB's_** ability to prevent leaking entities with some queries.

#### Example

```typescript
{
  model: {
      entity: "MallStoreCustom",
      version: "1",
      service: "mallstoredirectory"
  },
  attributes: {
    storeId: {
        type: "string"
    },
    mallId: {
        type: "string"
    },
    buildingId: {
        type: "string"
    },
    unitId: {
        type: "string"
    }
  },
  indexes: {
    locations: {
      pk: {
        field: "pk",
        composite: ["storeId"],        
        template: "sid_${storeId}"
      },
      sk: {
        field: "sk",
        composite: ["mallId", "buildingId", "unitId"],
        template: "mid_${mallId}#bid_${buildingId}#uid_${unitId}"
      }
    }
  }
}
```

#### Input

```json
{
    "storeId": "STOREVALUE",
    "mallId": "MALLVALUE",
    "buildingId": "BUILDINGVALUE",
    "unitId": "UNITVALUE"
};
```

#### Output

```json
{
  "pk": "sid_storevalue",
  "sk": "mid_mallvalue#bid_buildingvalue#uid_unitvalue"
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gDoFPnAC8cVBQDuKdJiwAKBBzhq4ICI2oAuRKvWGK87HrIBZAIZUqAZRjCAwgFchIMgBoDhtQDcKUHzAvGYAjJ7ePnwBvsC0FGYg1lRCUBSMwGn00FhkkbhehpYwDMDMzjAieio+6qkUAJKM1ZG1athgCeQCUMCoAOZ5bXiFbUk2TS3Dhh1dZD19g63qBctq5cBUGQOT+tPqs2YLA0Ntq8POqJi7Nftwh92lJ2t4+aNqfdoAHlV7bVQQWjFYL8KZ3MAAazBd3UBGA1Ga5EhERhMwo4CoxTmQUYAH0ACQIepNXAo1FqHjgCBBSp6ADa8wcaSaZAAui9DOc7nwoX9yXCEUcIWTyZUMVjEsA8YTxlQSQBiZhSgkIDZbRYK5zKwmXa6MUnvVGUyA0roM2UsjzkNXbfqW8i6mAs9kw3AvN2cjgFRD3SzMGhmLAQZxQXEwP00XGoSwgChkPAASk4HHqfAMADowBVlJFiYiyLYACoAeQASsgAGoAQQAMgBVZAijTJXYWWs16v1xuGm0a-MAITrDRrABEGgA5ADinYbTcdrbr44ahZnjYMuATGf6EEUSaAA)

### Composite Attribute and Index Considerations

As described in the above two sections ([Composite Attributes](#composing-attributes), [Indexes](#indexes)), ElectroDB builds your keys using the attribute values defined in your model and provided on your query. Here are a few considerations to take into account when thinking about how to model your indexes:

- Your table's primary Partition and Sort Keys cannot be changed after a record has been created. Be mindful of **not** to use Attributes that have values that can change as composite attributes for your primary table index.

- When updating/patching an Attribute that is also a composite attribute for secondary index, ElectroDB will perform a runtime check that the operation will leave a key in a partially built state. For example: if a Sort Key is defined as having the Composite Attributes `["prop1", "prop2", "prop3"]`, than an update to the `prop1` Attribute will require supplying the `prop2` and `prop3` Attributes as well. This prevents a loss of key fidelity because ElectroDB is not able to update a key partially in place with its existing values.

- As described and detailed in [Composite Attribute Arrays](/en/modeling/indexes#composite-attribute-arrays), you can use the `label` property on an Attribute shorten a composite attribute's prefix on a key. This can allow trim down the length of your keys.

## Index Mapping

ElectroDB supports many ways to map your indexes to your unique table. From indexes with only a partition key, to numeric sort keys, to existing tables with unconventional key structures. You can learn more about how your indexes map to your table definition on the [Schema page](/en/modeling/schema#indexes).

## Index Types

ElectroDB helps manage your key structure, and works to abstract out the details of how your keys are created/formatted. Depending on your unique data set, you may need ElectroDB to optimize your index for either [entity isolation](/en/modeling/indexes#isolated-indexes) (i.e. high volume of records per partition) or [entity relationships](/en/modeling/indexes#clustered-indexes) (i.e. high relationship density per partition).

This option changes how ElectroDB formats your keys for storage, so it is an important consideration to make early in your modeling phase. As a result, this choice cannot be simply walked back without requiring a migration. The choice between `clustered` and `isolated` depends wholly on your unique dataset and access patterns.

> You can use [Collections](/en/modeling/collections) with both `isolated` and `clustered` indexes. Isolated indexes are limited to only querying across the partition key while Clustered indexes can also leverage the Sort Key.

### Isolated Indexes

By default, and when omitted, ElectroDB will create your index as an `isolated` index. Isolated indexes optimizes your index structure for faster and more efficient retrieval of items within an individual Entity.

_Choose_ `isolated` if you have strong access pattern requirements to retrieve only records for only your entity on that index. While an `isolated` index is more limited in its ability to be used in a [collection](/en/modeling/collections), it can perform better than a `clustered` index if a collection contains a highly unequal distribution of entities within a collection.
_Do not choose_ `isolated` if the primary use-cases for your index is to query across entities -- this index type does limit the extent to which indexes can be leveraged to improve query efficiency.

### Clustered Indexes

When your index type is defined as `clustered`, ElectroDB will optimize your index for relationships within a partition. Clustered indexes optimize your index structure for more homogenous partitions, which allows for more efficient queries across multiple entities.

_Choose_ `clustered` if you have a high degree of grouped or similar data that needs to be frequently accessed together. This index works best in [collections](/en/modeling/collections) when member entities are more evenly distributed within a partition.
_Do not choose_ `clustered` if your need to query across entities is secondary to its primary purpose -- this index type limits the efficiency of querying your individual entity.

### Isolated vs Clustered

The following images illustrate the difference between an `isolated` and `clustered` index type. Each blue line represents an "employee" entity record and each yellow line represents a "task" entity record.

In this example, the following records have the same partition key composite attributes.

<div style={{ textAlign: "center" }}>
    <img src="/isolated.png" style={{ maxWidth: "75%" }} />
</div>

In an `isolated` index type, entities sharing a partition key do not co-mingle. While this is not ideal for querying across entities, it is ideal for efficently targetting (or "isolating") a single entity type.

<div style={{ textAlign: "center" }}>
    <img src="/clustered.png" style={{ maxWidth: "75%" }} />
</div>

In a `clustered` index type, entities sharing a partition key intermix (or "cluster") together. This is ideal for querying across entities, but not ideal for efficently targetting a single entity type.

### Indexes Without Sort Keys

When using indexes without Sort Keys, that should be expressed as an index _without_ an `sk` property at all. Indexes without an `sk` cannot have a collection, see [Collections](/en/modeling/collections) for more detail.

> It is generally recommended to always use Sort Keys when using ElectroDB as they allow for more advanced query opportunities. Even if your model doesn't _need_ an additional property to define a unique record, having an `sk` with no defined composite attributes (e.g. an empty array) still opens the door to many more query opportunities like [collections](/en/modeling/collections).

#### Example

```typescript
// ElectroDB interprets as index *not having* an SK.
{
  indexes: {
    myIndex: {
      pk: {
        field: "pk",
        composite: ["id"]
      }
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gwAhnwDWfOAF44qCgHcU6TFgAUHOInUa4ICI2oAuTdu0Ul2I2WFi+ZADRaTANwpQ+wXpYCM9x9r6uTsC0FJbWokJgYGR+uA4mQjAMwMwArjAUfEYIfhrAjNm52thgoeQCUMCoAOa+JiZQFACOqcCNBXAMqRRFcUWoQiBlOfXFWKWWFVW18aNwjS1tFB1dPaO4sbN5qPoAHpmFo408UB0jc2Cih3MaBMDUHWSXdTdwPOAQ7hlGANpk+WQALpFDQbG4AenBKBo9GIABEAEJwKoZKBgRowCQwAAWFDgVCEtFEcAgBDgQlQcAABmIqSC4JC4OiIKVYDgRJ1cfjCcTSeS4HxoPBRBQcLxyfTGdVgC5KVU9gA6XqxLR9DRIYTMGiWLAQVJQAD6mpoBoGQzIeA4AEpONxeAJkYwpOQQFgDcdoIwDQDODx+PAzXjpGRXabBhQDczWdh2FxwnwtArqhQYCokPk8FbE9UICobXGROJE2B0mnHXYZOHM9nc-mOPGFS1XFhEx7TmWM7gsxok7W2EA)

### Indexes With Sort Keys

When using indexes with Sort Keys, that should be expressed as an index _with_ an `sk` property. If you don't wish to use the Sort Key in your model, but it does exist on the table, simply use an empty for the `composite` property. An empty array is still very useful, and opens the door to more query opportunities and access patterns like [collections](/en/modeling/collections).

#### Example

```typescript
// ElectroDB interprets as index *having* SK, but this model does not assign any composite attributes to it.
{
  indexes: {
    myIndex: {
      pk: {
        field: "pk",
        composite: ["id"]
      },
      sk: {
        field: "sk",
        composite: []
      }
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gwAhnwDWfOAF44qCgHcU6TFgAUHOInUa4ICI2oAuTdu0Ul2I2WFi+ZADRaTANwpQ+wXpYCM9x9r6uTsC0FJbWokJgYGR+uA4mQjAMwMwArjAUfEYIfhrAjNm52thgoeQCUMCoAOa+JiZQFACOqcCNBXAMqRRFcUWoQiBlOfXFWKWWFVW18aNwjS1tFB1dPaO4sbN5qPoAHpmFo408UB0jc2Cih3MaBMDUHWSXdTdwPOAQ7hlGANpk+WQALpFDR9G5ia43O4PSaiF43AD0CLgAEE4BRwNg3iRIF8KHAhFAoEIcMAJAJgFQqBoQdokXAqrR2ikqDhUgECKkqAA6OAAYQAFnRRHAIOlOkK4LSNPSKLtBmAaBJmNQIAoYBA4AF8QK1eiaPQmMwpa84PT2Zk4GBGnd9hJeBL8XxoDBRBQcBqTa96QMiXqYJK+DxSqKCHAWq57nxudLsR88b9gTcNutYlowYhOkJmDRLFgxVAAPrCHMUQsDIZkPAcACUnG4vAEDMYUnIICwheO0EYhYBnB4-HgFfx0jI7fLgzL1ogpVgWHYXHCfC03OqFBgKiQ+TwNZX1QgKjri5E4hXYHSm+bdhkk53e4PR44S+5EagWBXXdOl+3uF3GlXD5sEAA)

### Numeric Keys

If you have an index where the Partition or Sort Keys are expected to be numeric values, you can accomplish this with the `template` property on the index that requires numeric keys. Define the attribute used in the composite template as type "number", and then create a template string with only the attribute's name.

For example, this model defines both the Partition and Sort Key as numeric:

#### Example

```typescript
const schema = {
  model: {
    entity: "numeric",
    service: "example",
    version: "1",
  },
  attributes: {
    number1: {
      type: "number", // defined as number
    },
    number2: {
      type: "number", // defined as number
    },
  },
  indexes: {
    record: {
      pk: {
        field: "pk",
        template: "${number1}", // will build PK as numeric value
      },
      sk: {
        field: "sk",
        template: "${number2}", // will build SK as numeric value
      },
    },
  },
};
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gwAhnwDWfOAF44qCgHcU6TFgAUCDnDggIjagC5EGzXApLsBsqgCuIClGC0yAGiOa+dgG4OKFigA8hcBpnVzgPOz5gXgsARjIjXBdNIRgGYGYrGAo+A3VjGRtmOxjc0M0AenK4XQJgWUY4EQKQIqgyuGwwH3JrFrsyOFDE0N7WgCZS-IqqmrqKBqbRu3bO7stC-s0hhKS4Ot0-bMnjKDpoRmP8sFFLqdrqC-JrkKnjSrg5YCoqOAyvhoACgBpRoSXp2BxhIRUKwUdqaLJBFJrAAkCCWUBiuGcg1emh44AgkSyBgA2us+piyABddrDV5iW75e5UR5kMQvPHvT7fX5Wf5wADKIMWNghtChMLheI6FCRJPIaIxY2xTlxeIJkGJ3XJyppdO2mlwHESiA6QmYNAsWAgVigAH1hJaKPbUIEKANcABKTjcXgCZqtGJSOAxMYAZgALABWTg8fjwZUhgBsAHYABwATgADL7hGI+EYAHQAcwoMDUgeKaqT3uLJYgKh9XHz4mLYEylYxMRrGygYzwXvrjebHFbfCLAEdYVAsMXTjwoIwu33g3XNEWijA5BRTCpQkhlQYkwBaUPZ7N4XaaQ99iZV-twADU58v9LgQ43DabbCAA)

### Index Casing

DynamoDB is a case-sensitive data store, and therefore it is common to convert the casing of keys to uppercase or lowercase prior to saving, updating, or querying data to your table. ElectroDB, by default, will lowercase all keys when preparing query parameters. For those who are using ElectroDB with an existing dataset, have preferences on upper or lowercase, or wish to not convert case at all, this can be configured on an index key field basis.

In the example below, we are configuring the casing ElectroDB will use individually for the Partition Key and Sort Key on the GSI "gsi1". For the index's PK, mapped to `gsi1pk`, we ElectroDB will convert this key to uppercase prior to its use in queries. For the index's SK, mapped to `gsi1sk`, we ElectroDB will not convert the case of this key prior to its use in queries.

```typescript
{
  indexes: {
    myIndex: {
      index: "gsi1",
      pk: {
        field: "gsi1pk",
        casing: "upper", // Acct_0120 -> ACCT_0120
        composite: ["organizationId"]
      },
      sk: {
        field: "gsi1sk",
        casing: "none", // Acct_0120 -> Acct_0120
        composite: ["accountId"]
      }
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3gBXPhSh84AXjioKAdxTpMWABQIOcOCAiNqALkTqNcCouz6yw0WQA0hjSKgA3YLQrmYAQz4BrD2DA2dnCOonzAvOYAjGSGuLYaHjAMwMyCMBR8+mpGcOkeIACSjFlBGthgbuQCUMCoAOYxOXFBllBFJTllWBXm1bUNQc050HUeqMAAXonhqO0GnbndlWR99Y1GQ0bENB2d5curA03xRgTAYjAAcvmV2XtLvclrgycaVF5XN7s5+481z8cghQQB5gFRvkZflUnkcNq84IxEhQABLnCjFeb3HrQ-4NeG4WInWo6AAeGQhrUymJyYG8EJyZ2oGLItMCCw0PHAEDC6X0AG0yCMxpNprwimQALrwnIAehlcHetG8cAgBDgAANaF5+uq4GBiBVYDgdAQPIIqDBxDAIAqILJRFqRKU8NK4D56adgEzet42ezOZAeZUBa1xVLnRo5XBBP4HV4MnBvBQcMwKARoBQ3R5nPUZTHERhcwBHQSiLD9CNwR39cwxw3rJovILbcnUozEigk8x1MKRWkAWh7wEiPn7HZJfppdLbDK9VGZQ77vtdHJIgcwwbIeUKjElK7gUcYEAy6CrAAsxnVM0mcNX6nBZGeTFmc3U82AC-0ZSWyxX2VXtXqcxUF4CgbDgZ1Nhyd0Z09b1yEXHxJ06ANuQ3fkyBbcCyALFE0V3cN-yjOs4xEcQbzgVN0ygTM+GzL982mYtSygct6krO86lrWMoAbDZBliDg4kQXIPGYHZyCwCBBCgAB9TxxIoWTUBuMg8AASk4bheAEXIKHyIpJHIAAVfSQAAISoUsYh4fghAcQypDIABVBxIgAJgAZhsnT4CFcYpgwMVGCMshTNoM8AGFoACbS7LgFtQpALAACUIBoHz4rOC5rhATMnIAKQ8JVMt094BFy-LyAAQUYGheLi3TgVBKhQoAK2K7wADoPDq0QAAF0gingoDALrmEmUr4Fw1EaJCpz3IABncyJ+0WgB2Nboi4SlDC6sA0lUIJtyKeFQ0YeF-JFILZgu5t0ooeFsoqm54XKz48vhZqwXhGb8JOXB1L2uoIGUTSdocPgup-Vi9spVQrsCmYikB4HQfBjhKWhlisD2ls+FUE6LoSh7UY0caKBge0TGUIIED+ubzCWla1oANjIKDEAZ9EmeW9yto5wwgfJkGwbYIA)

> Casing is a very important decision when modeling your data in DynamoDB. While choosing upper/lower is largely a personal preference, once you have begun loading records in your table it can be difficult to change your casing after the fact. Unless you have good reason, allowing for mixed case keys can make querying data difficult because it will require database consumers to always have a knowledge of their data's case.

| Casing Option | Effect                                                 |
| :-----------: | ------------------------------------------------------ |
|   `default`   | The default for keys is lowercase, or `lower`          |
|    `lower`    | Will convert the key to lowercase prior it its use     |
|    `upper`    | Will convert the key to uppercase prior it its use     |
|    `none`     | Will not perform any casing changes when building keys |

### Sparse Indexes

Sparse indexes are indexes that only contain a subset of the items on your main table. Sparse indexes are useful when you want to reduce the number of records your query must iterate over to find the records you are looking on a secondary index. By having fewer records on a secondary index, you can improve the performance of your queries and reduce the cost of your table.

ElectroDB manages which secondary indexes are written to your DynamoDB table based on the attributes you provide to your query; this includes adding runtime constraints to ensure consistency between your index key values and its constituent composite attributes. If you wish to prevent an index from being written, you can define a `condition` callback on your index. The provided callback will be invoked at query-time, passed all attributes set on that mutation, and if it returns `false` the keys for that index be deleted -- removing the item's presence from that index.

> _Note: Beginning with ElectroDB version 2.14.0, the presence of a `condition` callback will add a _new_ runtime validation check on all mutations. When a conditional index is defined, all member attributes of the index must be provided if a mutation operation affects one of the attributes. This is to ensure that the index is kept in sync with the item's attributes. If you are unable to update/patch all member attributes, because some are readOnly, you can also use the [composite](/en/mutations/patch#composite) method on [update](/en/mutations/update#composite) and [patch](/en/mutations/patch#composite). More information and the discussion around the reasoning behind this change can be found [here](https://github.com/tywalch/electrodb/issues/366). Failure to provide all attributes will result in an [Invalid Index Composite Attributes Provided Error](/en/reference/errors#invalid-index-composite-attributes-provided)._

#### Example

This example shows an index called `myIndex` which has a provided `condition` callback. The `attr` argument contains all attributes provided to the mutation method. This includes all attributes for a `put`, `create`, or `upsert` and/or all attributes `set` on an `update` or `patch`.

```typescript
{
  indexes: {
    myIndex: {
      index: "gsi1",
      condition: (attr) => attr.type === "closed"
      pk: {
        field: "gsi1pk",
        composite: ["organizationId"]
      },
      sk: {
        field: "gsi1sk",
        composite: ["type", "accountId"]
      }
    }
  }
}
```

### Index Scoping

When designing your indexes with Single Table Design, you may find yourself in a situation where the composite attributes of one entity's partition key are the same as another entity. While in most cases this would be a deliberate choice, made to more easily query across entities via [collections](en/modeling/collections), there are times when you might desire further isolation between entities. This scenario is most commonly encountered when the partition key of both entities are defined with an empty composite array. When further isolation between entities on an index is necessary, you can use the `scope` property on your index to directly impact how records are partitioned.

> Note: the `scope` concept came from an RFC that was thoughtfully put forward by [@Sam3d](https://github.com/sam3d) in [Issue #290](https://github.com/tywalch/electrodb/issues/290), which can be read for further context. Thank you, Brooke for your contribution!

The example below demonstrates how to use the `scope` property to isolate records on an index. In this example, the `organization` entity and the `user` entity both have a partition key of `pk` with an empty composite array. This means that both entities will share the same partition key, and will be stored in the same partition. This is not ideal, as it means that a query for users may also impact the throughput of a query for organizations. To isolate these entities, we can use the `scope` property on the index to further isolate the partition key for each entity.

```typescript
const organization = new Entity({
  model: {
    entity: "organization",
    service: "taskapp",
    version: "1"
  },
  attributes: {
    organizationId: {
      type: "string"
    },
  },
  indexes: {
    myIndex: {
      scope: "org", // <--- Scope is set to unique value "org"
      pk: {
        field: "pk",
        composite: []
      },
      sk: {
        field: "sk",
        composite: ["organizationId"]
      }
    }
  }
}, { table: "your_table_name" });

const user = new Entity({
  model: {
    entity: "user",
    service: "taskapp",
    version: "1"
  },
  attributes: {
    userId: {
      type: "string"
    },
  },
  indexes: {
    myIndex: {
      scope: "user", // <--- Scope is set to unique value "user"
      pk: {
        field: "pk",
        composite: []
      },
      sk: {
        field: "sk",
        composite: ["userId"]
      }
    }
  }
}, { table: "your_table_name" });
```

Without the use of `scope`, both entities would share the same partition key and would be stored in the same partition. With the use of `scope`, each entity will have a unique partition key and will be stored in a separate partition.

#### Without Scope

Without a `scope` value, notice the partition key value at ExpressionAttributeValues[':pk'] is the same for both queries. This occurs when composite attribute array for a partition key matches between entities. Keep in mind that, in most cases, this is desirable because it helps enable querying across entities via [collections](/en/modeling/collections). However, in some cases, this may not be desirable which is why the `scope` property exists.

```typescript
organization.query.myIndex({organizationId: '123'}).go();
// {
//     "KeyConditionExpression": "#pk = :pk and #sk1 = :sk1",
//     "TableName": "your_table_name",
//     "ExpressionAttributeNames": {
//         "#pk": "pk",
//         "#sk1": "sk"
//     },
//     "ExpressionAttributeValues": {
//         ":pk": "$taskapp",
//         ":sk1": "$organization_1#organizationid_org123"
//     }
// }
```

```typescript
user.query.myIndex({userId: '456'}).go();
// {
//     "KeyConditionExpression": "#pk = :pk and #sk1 = :sk1",
//     "TableName": "your_table_name",
//     "ExpressionAttributeNames": {
//         "#pk": "pk",
//         "#sk1": "sk"
//     },
//     "ExpressionAttributeValues": {
//         ":pk": "$taskapp",
//         ":sk1": "$organization_1#organizationid_org123"
//     }
// }
```

#### With Scope

Notice the value at ExpressionAttributeValues[':pk'] now contains the `scope` value that was provided by the user on the index definition.

```typescript
organization.query.myIndex({organizationId: '123'}).go();
// {
//     "KeyConditionExpression": "#pk = :pk and #sk1 = :sk1",
//     "TableName": "your_table_name",
//     "ExpressionAttributeNames": {
//         "#pk": "pk",
//         "#sk1": "sk"
//     },
//     "ExpressionAttributeValues": {
//         ":pk": "$taskapp_org",
//         ":sk1": "$organization_1#organizationid_org123"
//     }
// }
```

```typescript
user.query.myIndex({userId: '456'}).go();
// {
//     "KeyConditionExpression": "#pk = :pk and #sk1 = :sk1",
//     "TableName": "your_table_name",
//     "ExpressionAttributeNames": {
//         "#pk": "pk",
//         "#sk1": "sk"
//     },
//     "ExpressionAttributeValues": {
//         ":pk": "$taskapp_user",
//         ":sk1": "$organization_1#organizationid_org123"
//     }
// }
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3jQA5gENUwAF4iMvOAF44qCgHcU6TFgAUCDnDggIjagC5EuvXArrspssLGTpwXmQA05vXwpQAbsFoUtjAifADWImBgbh5wPt58zqi2AIxk5rjuetIMwMwArjAUfKY6FnD24lIyqACSjCUxethggeQCUMCoQmllGemZcJ1GAB5FDWUgWDWoI+NlcHw8LbbCbnAA9OtwADwAtPtwAMpLFIN8CxTwMBBweeIAjnmnPiJUT+SrjXBgoXPzhMBqPVyD9ov89DxwBAEoVTABtAC6Xz6-zCf3mBEBVGBZDCYPBkMgMNacLsUFElScvDqZCR-1wMQZegZGUQcGCzBotiwEDyUAA+hyaPzUCIQBQyHgAJScbi8AS3LxQeSKFRqDDYbTmAxGKjoqwarC2PJK-Gebx+AJBELhSJm2LxRIpHp4AbZDr5QrFMxlE3eOroppYZZtHJdF3MgYowYzCijb2lCyTaazH3zRYQENkP1QNabHb7XZHE5nC5XG53YCPZ6vd7Z01fH6BiyYoG2UEDf6E6GYEl0+bRspotP-VvY2x4zvzbvE+H1-2MWnIxnpDispBC1pkHl8wUiTkUEViiXS2UVRzVAB01agWEvydjw205XJDiqiQDcAA5MkAEwAZi-aVLyECBNBlDgc2vJ5b3vKZH2fHNPy-AAWABWAA2IDcClECwJlIA)

### Attributes as Indexes

It may be the case that an index field is also an attribute. For example, if a table was created with a Primary Index partition key of `accountId`, and that same field is used to store the `accountId` value used by the application. The following are a few examples of how to model that schema with ElectroDB:

> If you have the unique opportunity to use ElectroDB with a new project, it is strongly recommended to use generically named index fields that are separate from your business attributes.

When your attribute's name, or [`field` property](/en/modeling/attributes#attribute-options) on an attribute, matches the `field` property on an indexes' `pk` or `sk` ElectroDB will forego its usual index key prefixing.

#### Example

```typescript
{
  model: {
    entity: "your_entity_name",
    service: "your_service_name",
    version: "1"
  },
  attributes: {
    accountId: {
      type: "string"
    },
    productNumber: {
      type: "number"
    }
  },
  indexes: {
    products: {
      pk: {
        field: "accountId",
        composite: ["accountId"]
      },
      sk: {
        field: "productNumber",
        composite: ["productNumber"]
      }
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbwKIDsbBgTwL5wGZQQhwDkApgDZkDGMhAJgEYkDcAUG9RCgM7zAoAbmTTRMcALxwUZAO5xU6LAAoEbOHBAR6lAFyJ1GuCKWZ9AIkwQArlAD6JjJjsoAhiDLmANIY08yUILA1GQWVrZ2-oHBZC7unj5GcMJQPMDcFgCM5obYiXCuMHTAjNYwZDz6akmu1FzWaACS9FW+RlhgoXDmfFACAOY5SXltYAzWtABy1iCMAa1JGh1d5igzc1BDRti5+QI6AB4VC0Zj2hMwlQaLcGAA1ic3eMCULd219U303m1JXOAQNLlfQAbXMHxsX3MAF1fnARjceA9rjd8C8KG9zGd6BdprMAj9UXB-pAgV0wdjcesCbCbjthrk2HlEDBXIwqPoSCBMAAVNlUEjYACU7E43D4BTqkJgzUk7zqMAAtAAWACsAGYAAzmdhcXjwSlTalQOUAJk1msyooEwlEUEwhgAdGAyqpJZ8ZfQvLdxkb8SbhU7+hBlCKODaTGJHQBHawBB1JZ2+y6qCENT2BxPB0PsIA)

Another approach allows you to use the `template` property, which allows you to format exactly how your key should be built when interacting with DynamoDB. In this case `composite` is optional when using `template`, but including it helps with TypeScript typing.

#### Example

```typescript
{
  model: {
    entity: "your_entity_name",
    service: "your_service_name",
    version: "1"
  },
  attributes: {
    accountId: {
      type: "string" // string and number types are both supported
    }
  },
  indexes: {
    "your_access_pattern_name": {
      pk: {
        field: "accountId",
        composite: ["accountId"],
        template: "${accountId}"
      },
      sk: {...}
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3jBUANwrpoOALxxUFAO4p0mLAAoEHOJrggIjagC5EGrSbEZshslggBXKAH0zy+6gCGICmQA0xk5r4UUMLAtBSW1nb2AUEhFC7unj5+WqJQfMC8lgCMZL54SVquMAzAzDYwFHyG6smarrQ8NugAkozVebVw2GBh5AJQQgDmZHAdWrheyWOaYEw29AByNiDMge2dJt29ZKjLq1C5nRN5xyZCegAeleu1s7rzMFVGG1pgANY3LyYEwNRt5PVGi1GN5phsKuAqEVtgASBCA2zA3Cgr5+HjgCDpCqGADaZARTRgrTIAF0Ci9Tl8+B9nqitD8-pY7owHksVoEUXTNBCwFDseQ4czWXtAsjyXT0ZAsb08ULFiKDmSwSZcGDVSqOBNEF1XMwaOFbA4YLqaPEPCNcABKTjcXgCOAE4FwaT4howAC0ABYAKwAZgADOxbfx4HKYGz9s64AAmf3+rI2oSicRQLDGAB0YHKagdDURRMYkzDEcCeEtGcGEBU1q4SbMEnTAEcbIE0yZM3N6Hw1I6C1a8unK9XOEA)

### Advanced use of template

When your `string` attribute is also an index key, and using key templates, you can also add static prefixes and postfixes to your attribute. Under the covers, ElectroDB will leverage this template while interacting with DynamoDB but will allow you to maintain a relationship with the attribute value itself.

#### Example

```typescript
{
  model: {
    entity: "your_entity_name",
    service: "your_service_name",
    version: "1"
  },
  attributes: {
    accountId: {
      type: "string" // only string types are both supported for this example
    },
    organizationId: {
      type: "string"
    },
    name: {
      type: "string"
    }
  },
  indexes: {
    "your_access_pattern_name": {
      pk: {
        field: "accountId",
        composite: ["accountId"],
        template: "prefix_${accountId}_postfix"
      },
      sk: {
        field: "organizationId",
        composite: ["organizationId"]
      }
    }
  }
}
```

[![Try it out!](https://img.shields.io/badge/electrodb-try_out_this_example_›-%23f9bd00?style=for-the-badge&logo=amazondynamodb&labelColor=1a212a)](https://electrodb.fun/?#code/JYWwDg9gTgLgBAbzgUQHY2DAnnAvnAMyghDgCIBTAGwoGMZiATAIzIG4AoD2iVAZ3jBUANwrpoOALxxUFAO4p0mLAAoEHOHBARG1AFyINmuGIzYDZLBACuUAPqnld1AEMQFMgBojmvhSjCwLQUFla2dn4BQRTObh7exnCiUHzAvBYAjGRGuAlwLjAMwMzWMBR8BuqJLrQ81ugAkoyVPsbYYCHkAlBCAOZkcAD0g3C8VDjdfXDt5flQFHDMEDAAFnB81mCQsBSMhNDTK8B8JgAebmA0rbmt0L0uqMAAXgVpqE0tiZozFpOo-dc8ppXO5Pl8fl0iv9solcDk8kJdKdymDNDU6ugKoYvsYwABrVE44wEYDUZrkO4PZ6vXhNLytIlwHjgCCpMoGADaZEpjxeGFpjDIAF0GcYboy+ATsYziaSqOSyOibI1BUCZczIGzOlylfUYHShWrGWVwFQCp0yGB5iTTnYACQIXUq3B2TUwG0wonisU5Di5RDTFzMGihGz2GBBmixdwDXAASk43F4AnytWV+r20kVtRgAFoACwAVgAzAAGdhJ-jwHnU-nvTMUqC9XMATjLWUTQlE4igWCMADowKU1KmMRnPKMm1S+W8mng4wPehAVAmuMM4HJMCsbPAnRn1ptLqTGBwu6YJP2AI7Wfx9xL9vd8NQ1mcC+OtftLleJ9d7ucbLYqGPfIThcchmAoXohD4bIzx7LArxvXsP0fZ8p15Gl63fe8IKg-g1D-RhsOMT9l1XDhfzTPV-0PIDdhA8gCGsKhxjgPFUAgORUFgkRz17RDbxQqjMTQ+4MLrJoJ0I4jNFI78gA)

ElectroDB will accept a `get` request like this:

```typescript
await myEntity
  .get({
    accountId: "1111-2222-3333-4444",
    organizationId: "AAAA-BBBB-CCCC-DDDD",
  })
  .go();
```

Query DynamoDB with the following params (note the pre/postfix on `accountId`):

> ElectroDB defaults keys to lowercase, though this can be configured using [Index Casing](#index-casing).

```json
{
  "Key": {
    "accountId": "prefix_1111-2222-3333-4444_postfix",
    "organizationId": "aaaa-bbbb-cccc-dddd"
  },
  "TableName": "your_table_name"
}
```

When returned from a query, however, ElectroDB will return the following and trim the key of it's prefix and postfix:

```json
{
  "accountId": "prefix_1111-2222-3333-4444_postfix",
  "organizationId": "aaaa-bbbb-cccc-dddd"
}
```
